import { HTMLAst, HTMLAstTag, HTMLAstText, RenderInst } from './declare'
import { Reactive, ReactiveInstance } from './reactive'

export default function render<T extends RenderInst>(ast: HTMLAst, inst: T) {
  const TEMPLATE_LITERAL = /\{\{\s*.+?\s*\}\}/g

  function map(item: HTMLAstText | HTMLAstTag, parent: HTMLElement | DocumentFragment) {
    // 文本节点
    if (item.tag === '#text') {
      const { data, bind } = item as HTMLAstText
      if (bind && inst.$data instanceof Reactive) {
        const match = data.match(TEMPLATE_LITERAL)
        if (match) {
          const temp = data.split(TEMPLATE_LITERAL)
          for (let i = 0; i < temp.length; i++) {
            if (temp[i].length) {
              parent.append(new Text(temp[i]))
            }
            const m = match.shift()
            if (m) {
              const prop = m.replace(/(\{\{\s*|\s*\}\})/g, '')
              const node = new Text('')
              parent.append(node)
              inst.$data.$immediate(prop, function (nv) {
                node.data = nv
              })
            }
          }
        }
      } else {
        const node = new Text(data)
        parent.append(node)
      }
    }
    // Element 节点
    else {
      const { tag, ref, attrs, bind, bool, style, classList, event, children } = item as HTMLAstTag
      const node = document.createElement(tag)
      parent.append(node)
      // refs
      if (ref && typeof inst.$refs === 'object') {
        inst.$refs[ref] = node
      }
      // 普通的 attribute
      if (typeof attrs === 'object') {
        for (let key in attrs) {
          node.setAttribute(key, attrs[key])
        }
      }
      if (inst.$data instanceof Reactive) {
        // 普通 attribute 双向绑定
        if (typeof bind === 'object') {
          for (let key in bind) {
            const prop = bind[key]
            let cb: (nv: any, ov: any) => void
            switch (key) {
              case 'if': {
                const comment = document.createComment('')
                cb = (nv) => {
                  if (nv) {
                    comment.parentNode?.insertBefore(node, comment)
                    comment.remove()
                  } else {
                    node.parentNode?.insertBefore(comment, node)
                    node.remove()
                  }
                }
                break
              }
              case 'show': {
                cb = (nv) => {
                  node.style.setProperty('display', nv ? 'block' : 'none')
                }
                break
              }
              case 'class': {
                cb = (nv: any, ov: any) => {
                  node.classList.remove(ov)
                  if (typeof nv == 'string') {
                    node.classList.add(nv)
                  }
                }
                break
              }
              default:
                cb = (nv) => {
                  nv === null || nv === undefined ? node.removeAttribute(key) : node.setAttribute(key, nv as string)
                }
                break
            }
            inst.$data.$immediate(prop, cb)
          }
        }
        // boolean attribute 双向绑定
        if (typeof bool === 'object') {
          for (let key in bool) {
            const prop = bool[key]
            inst.$data.$immediate(prop, (nv) => {
              nv === true ? node.setAttribute(key, '') : node.removeAttribute(key)
            })
          }
        }
        // style attribute 双向绑定
        if (typeof style === 'object') {
          for (let key in style) {
            const prop = style[key]
            inst.$data.$immediate(prop, (nv) => {
              nv === null ? node.style.removeProperty(key) : node.style.setProperty(key, nv)
            })
          }
        }
        // class attribute 双向绑定
        if (typeof classList === 'object') {
          for (let key in classList) {
            const prop = classList[key]
            inst.$data.$immediate(prop, (nv) => {
              nv ? node.classList.add(key) : node.classList.remove(key)
            })
          }
        }
      }
      // 事件绑定
      if (typeof event === 'object' && typeof inst.$listener === 'object') {
        for (let key in event) {
          node.addEventListener(key, function (e) {
            // @ts-ignore
            const listener = Reflect.get(inst.$listener, event[key])
            if (typeof listener === 'function') {
              Reflect.apply(listener, inst, [{ ev: e, ctx: inst, target: node }])
            }
          })
        }
      }
      // childNodes
      if (Array.isArray(children)) {
        children.forEach((item) => map(item, node))
      }
    }
  }

  const frag = document.createDocumentFragment()
  ast.forEach((item) => map(item, frag))

  return frag
}
